package com.yesep.learn.design.behavioral.interpreter.calculator;

import java.util.*;

public class CalculatorUtil {
    public static IExpression parseExpression(String expressionString, Context context) {
        List<IExpression> expressions = new ArrayList<>();
        Stack<Character> operatorStack = new Stack<>();

        for (int i = 0; i < expressionString.length(); i++) {
            char currentChar = expressionString.charAt(i);

            if (Character.isWhitespace(currentChar)) {
                continue;
            }

            if (Character.isDigit(currentChar)) {
                int start = i;
                while (i < expressionString.length() && Character.isDigit(expressionString.charAt(i))) {
                    i++;
                }
                int value = Integer.parseInt(expressionString.substring(start, i));
                expressions.add(new NumberExpression(value));
                i--;
            } else if (Character.isLetter(currentChar)) {
                int start = i;
                while (i < expressionString.length() && Character.isLetter(expressionString.charAt(i))) {
                    i++;
                }
                String name = expressionString.substring(start, i);
                if (i < expressionString.length() && expressionString.charAt(i) == '(') {
                    i++; // Skip the '('
                    int argStart = i;
                    while (i < expressionString.length() && expressionString.charAt(i) != ')') {
                        i++;
                    }
                    String argument = expressionString.substring(argStart, i);
                    IExpression argumentExpr = parseExpression(argument, context);
                    expressions.add(new FunctionCallExpression(name, argumentExpr));
                } else {
                    expressions.add(new VariableExpression(name));
                }
                i--;
            } else if (currentChar == '(') {
                operatorStack.push(currentChar);
            } else if (currentChar == ')') {
                while (!operatorStack.isEmpty() && operatorStack.peek() != '(') {
                    char operator = operatorStack.pop();
                    IExpression right = expressions.remove(expressions.size() - 1);
                    IExpression left = expressions.remove(expressions.size() - 1);
                    expressions.add(createOperatorExpression(operator, left, right));
                }
                if (!operatorStack.isEmpty() && operatorStack.peek() == '(') {
                    operatorStack.pop(); // Pop the '('
                }
            } else if (OperatorType.isOperator(Character.toString(currentChar))) {
                while (!operatorStack.isEmpty() && operatorStack.peek() != '(' &&
                        OperatorType.fromSymbol(Character.toString(operatorStack.peek())).getPrecedence() >=
                                OperatorType.fromSymbol(Character.toString(currentChar)).getPrecedence()) {
                    char operator = operatorStack.pop();
                    IExpression right = expressions.remove(expressions.size() - 1);
                    IExpression left = expressions.remove(expressions.size() - 1);
                    expressions.add(createOperatorExpression(operator, left, right));
                }
                operatorStack.push(currentChar);
            } else {
                throw new IllegalArgumentException("Unsupported token: " + currentChar);
            }
        }

        while (!operatorStack.isEmpty()) {
            char operator = operatorStack.pop();
            IExpression right = expressions.remove(expressions.size() - 1);
            IExpression left = expressions.remove(expressions.size() - 1);
            expressions.add(createOperatorExpression(operator, left, right));
        }

        return expressions.get(0);
    }

    private static IExpression createOperatorExpression(char operator, IExpression left, IExpression right) {
        OperatorType opType = OperatorType.fromSymbol(Character.toString(operator));
        switch (opType) {
            case ADDITION:
                return new AdditionExpression(left, right);
            case SUBTRACTION:
                return new SubtractionExpression(left, right);
            case MULTIPLICATION:
                return new MultiplicationExpression(left, right);
            case DIVISION:
                return new DivisionExpression(left, right);
            default:
                throw new IllegalArgumentException("Unsupported operator: " + operator);
        }
    }


    public static double calculate(String expressionString, Context context) {
        IExpression expression = parseExpression(expressionString, context);
        return expression.interpret(context);
    }
}